package fields;

import static fields.Util.*;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class Painter extends JComponent implements ActionListener {
    private static final Color[] REDS = new Color[256];
    private static final Color[] BLUES = new Color[256];

    static {
        for (int i = 0; i < 256; i++) {
            REDS[i] = new Color(255, 0, 0, 255 - i);
            BLUES[i] = new Color(0, 0, 255, 255 - i);
        }
    }

    private List<Charge> charges;
    private List<Drawer> drawers;
    private Random r = new Random();
    private Timer t;

    public Painter() {
        charges = initCharges();
        drawers = initDrawers(charges);
        t = new Timer(DELAY, this);
        t.setRepeats(true);
        t.start();
    }

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == t) {
            for (Charge charge : charges) {
                move(charge);
            }
            repaint();
        }
    }

    private void move(Charge charge) {
        Point2D p = charge.getPoint();
        if (p.getX() > getWidth())
            p.setLocation(getWidth(), p.getY());
        if (p.getY() > getHeight())
            p.setLocation(p.getX(), getHeight());
        if (p.getX() < 0)
            p.setLocation(0, p.getY());
        if (p.getY() < 0)
            p.setLocation(p.getX(), 0);
        if (p.getX() + charge.getVx() > getWidth() || p.getX() + charge.getVx() < 0)
            charge.setVx(-charge.getVx());
        if (p.getY() + charge.getVy() > getHeight() || p.getY() + charge.getVy() < 0)
            charge.setVy(-charge.getVy());
        p.setLocation(p.getX() + charge.getVx(), p.getY() + charge.getVy());
        drawers.clear();
        drawers.addAll(initDrawers(charges));
    }

    @Override
    public void setVisible(boolean aFlag) {
        if (aFlag && !t.isRunning())
            t.start();
        if (!aFlag && t.isRunning())
            t.stop();
    }

    private java.util.List<Charge> initCharges() {
        java.util.List<Charge> charges = new ArrayList<Charge>();
        int count = 2 + r.nextInt(3);
        boolean good_state = false;
        while(!good_state) {
            charges.clear();
            for (int i = 0; i < count; i++) {
                double ch = ((double) getNonZero(CHARGE_VARIETY)) / CHARGE_VARIETY * Util.CHARGE_ORDER;
                Charge charge = new Charge(new Point2D.Double(r.nextInt(PREF_WIDTH), r.nextInt(PREF_HEIGHT)), ch);
                charge.setVx((double) getNonZero(SPEED_VARIETY) / SPEED_VARIETY * SPEED_ORDER);
                charge.setVy((double) getNonZero(SPEED_VARIETY) / SPEED_VARIETY * SPEED_ORDER);
                charges.add(charge);
            }
            Boolean b = null;
            for (int i = 0; i < count; i++) {
                if (b==null )
                    b = Math.signum(charges.get(i).getCharge())>0;
                else {
                    if (b ^ Math.signum(charges.get(i).getCharge())>0) {
                        good_state = true;
                        break;
                    }
                }
            }
        }
        return charges;
    }

    private List<Drawer> initDrawers(List<Charge> charges) {
        List<Drawer> drawers = new ArrayList<Drawer>();
        for (Charge charge : charges) {
            for (int j = 0; j < Util.CHARGE_LINE_COUNT; j++) {
                double angle = Math.PI * 2 * j / Util.CHARGE_LINE_COUNT;
                Point2D.Double p = new Point2D.Double(charge.getPoint().getX() + Util.CHARGE_LINE_START_RADIUS * Math.cos(angle), charge.getPoint().getY() + Util.CHARGE_LINE_START_RADIUS * Math.sin(angle));
                drawers.add(new Drawer(p, DrawerType.FORCE_INC));
                drawers.add(new Drawer(p, DrawerType.FORCE_DEC));
//                drawers.add(new Drawer(p, Drawer.Type.FORCE_LEFT));
//                drawers.add(new Drawer(p, Drawer.Type.FORCE_RIGHT));
            }
        }
        return drawers;
    }

    private int getNonZero(int variety) {
        int n = 0;
        while (n == 0)
            n = r.nextInt(variety * 2 + 1) - variety;
        return n;
    }

    public void paint(Graphics g) {
        g.setColor(Color.GRAY);
        g.fillRect(0, 0, getWidth(), getHeight());
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.setColor(Color.LIGHT_GRAY);
        for (Drawer drawer : drawers) {
            List<PowerPoint> list = drawer.getDraw(charges, getBounds());
            if (!USE_ALPHA)
                g.setColor(drawer.getType() != DrawerType.FORCE_DEC ? Color.BLUE : Color.RED);
            for (int i = 0; i < list.size() - 1; i++) {
                PowerPoint p1 = list.get(i);
                PowerPoint p2 = list.get(i + 1);
//                g.setColor(i % 2 == 1 ? Color.BLACK : Color.ORANGE);//todo debug
                //experimental
                if (USE_ALPHA)
                    g.setColor(getColor((p1.getPower() + p2.getPower()) / 2, drawer.getType() == DrawerType.FORCE_DEC));
                g.drawLine((int) p1.getX(), (int) p1.getY(), (int) p2.getX(), (int) p2.getY());
            }
        }
        //-Math.log10(p.getPower()) is in ~2..~9
//        g.setColor(Color.WHITE);
//        for (Drawer drawer : drawers) {//todo debug
//            g.fillOval((int) drawer.getStartPoint().getX() - 1, (int) drawer.getStartPoint().getY() - 1, 3, 3);
//        }
        for (Charge charge : charges) {
            g.setColor(Math.signum(charge.getCharge()) > 0 ? Color.RED : Color.BLUE);
            g.fillOval((int) charge.getPoint().getX() - 2, (int) charge.getPoint().getY() - 2, 5, 5);
        }
    }

    private static Color getColor(double power, boolean red) {
        int n = Math.max(0, Math.min(255, (int) ((-Math.log10(power) - 1.2) * 40)));
        return red ? REDS[n] : BLUES[n];
    }

    public Dimension getPreferredSize() {
        return new Dimension(Util.PREF_WIDTH, Util.PREF_HEIGHT);
    }
}
